/**
 *  WordItem.java : IceBird project
 *  19:02:39 
 */
package icebird.compiler.ncomp.vs;

import sun.reflect.generics.reflectiveObjects.NotImplementedException;
import icebird.compiler.ncomp.CodeCompiler;
import icebird.compiler.ncomp.X86Assembler;
import icebird.compiler.ncomp.X86Register;
import icebird.compiler.ncomp.X86RegisterPool;
import icebird.compiler.ncomp.X86Register.GPR;
import icebird.metadata.BasicType;

/**
 * Represents one word(4 bytes) item.
 * @author Sergey Shulepoff[Knott]
 */
public abstract class WordItem extends Item {
	
	protected WordItem(Location l) {
		super(l);
	}

	/* (non-Javadoc)
	 * @see icebird.compiler.ncomp.vs.Item#getCategory()
	 */
	public final int getCategory() {
		return 1;
	}

	/**
	 * Gets GP Register.
	 * @return GPR
	 */
	protected GPR getGPR() {
		if (!getLocation().isGPR()) {
			throw new IllegalStateException();
		}
		
		return ((GPRLocation)getLocation()).getGPR();
	}

	/* (non-Javadoc)
	 * @see icebird.compiler.ncomp.vs.Item#clone()
	 */
	public Item clone() {
		X86Assembler asm = X86Assembler.getAsm();
		Location loc = getLocation();
		
		// Location is GPR
		if (loc.isGPR()) {
			GPR reg = CodeCompiler.getCompiler().request(getType());
			asm.move(getGPR(), reg);
			
			return getGPRWord(reg, getType());
		}
		
		// Location is local
		if (loc.isLocal()) {
			LocalLocation l = (LocalLocation)getLocation();
			return getLocal(l.getOffset(), getType());
		}
		
		// Location is constant.
		if (loc.isConstant()) {
			return cloneConstant();
		}
		
		// Location is stack.
		if (loc.isStack()) {
			asm.push(X86Register.ESP, 0);
			return getStack(getType());
		}
		
		throw new IllegalStateException();
	}
	
	
	
	/* (non-Javadoc)
	 * @see icebird.compiler.ncomp.vs.Item#load()
	 */
	public void load() {
		// Already loaded.
		if (getLocation().isGPR()) return;
		
		X86RegisterPool p = CodeCompiler.getCompiler().getPool();
		X86Register reg = p.request(getType());
		
		if (reg == null) {
			CodeCompiler.getCompiler().getVS().push();
			reg = p.request(getType());
		}
		
		loadTo(reg);
	}
	
	/**
	 * Load this data to register.
	 * @return void
	 */
	public final void loadTo(X86Register reg) {
		X86Assembler asm = X86Assembler.getAsm();
		VirtualStack s = CodeCompiler.getCompiler().getVS();
		
		// Is location is General Purpose Register?
		if (getLocation().isGPR()) {
			// if destanation register is not source. 
			if (reg != ((GPRLocation)getLocation()).getGPR()) {
				asm.move(reg, getGPR());
				cleanup();
			}
		}
		
		// Is location is local variable(EBP releative)?
		if (getLocation().isLocal()) {
			X86Register ebp = X86Register.EBP;
			
			asm.move(reg, ebp, ((LocalLocation)getLocation()).getOffset());
		}
		
		// Is location is purely virtual(Constant value, known at compile-time).
		if (getLocation().isConstant()) {
			loadConstant(reg);
		}
		
		// Is location is FPU stack?
		if (getLocation().isFPUStack()) {
			FPUStack.fxch(s.getFPUStack(), this);
			s.getFPUStack().pop(this);
			
			asm.lea(X86Register.ESP, X86Register.ESP, -4);
			popFPU(X86Register.ESP, 0);
			asm.pop(reg);
		}
		
		// Is located on stack?
		if (getLocation().isStack()) {
			asm.pop(reg);
		}
		
		// Set location to GPR.
		setLocation(new GPRLocation((GPR)reg));
	}
	
	// Clean up resources used by this item.
	private void cleanup() {
		if (getLocation().isGPR()) {
			GPRLocation gpr = (GPRLocation) getLocation();
			CodeCompiler.getCompiler().getPool().release(gpr.getGPR());
		}
	}
	
	

	/* (non-Javadoc)
	 * @see icebird.compiler.ncomp.vs.Item#loadGPR()
	 */
	public final void loadGPR() {
		// Already loaded.
		if (getLocation().isGPR()) return;
		
		X86RegisterPool p = CodeCompiler.getCompiler().getPool();
		X86Register reg = p.request(getType());
		
		if (reg == null) {
			CodeCompiler.getCompiler().getVS().push();
			reg = p.request(getType());
		}
		
		loadTo(reg);
	}
	
	

	/* (non-Javadoc)
	 * @see icebird.compiler.ncomp.vs.Item#loadSSE()
	 */
	public void loadSSE() {
		throw new NotImplementedException();
	}
	
	/*
	 * (non-Javadoc)
	 * @see icebird.compiler.ncomp.vs.Item#push()
	 */
	public void push() {
		X86Assembler asm = X86Assembler.getAsm();
		VirtualStack stack = CodeCompiler.getCompiler().getVS();
		
		// Push register contents into stack.
		if (getLocation().isGPR()) {
			asm.push(((GPRLocation)getLocation()).getGPR());
		}
		
		// Push local variable.
		if (getLocation().isLocal()) {
			asm.push(X86Register.EBP, ((LocalLocation)getLocation()).getOffset());
		}
		
		// Push get location.
		if (getLocation().isConstant()) {
			pushConstant();
		}
		
		// Pop from FPU and push into actual stack.
		if (getLocation().isFPUStack()) {
			FPUStack fpuStack = stack.getFPUStack();
			FPUStack.fxch(fpuStack, this);
			
			fpuStack.pop(this);
			
			// Convert & move to new space on normal stack
			asm.lea(X86Register.ESP, X86Register.ESP, -4);
			popFPU(X86Register.ESP, 0);
		}
		
		cleanup();
		setLocation(new StackLocation());
	}
	
	

	/* (non-Javadoc)
	 * @see icebird.compiler.ncomp.vs.Item#pushFPU()
	 */
	public final void pushFPU() {
		X86Assembler asm = X86Assembler.getAsm();
		VirtualStack stack = CodeCompiler.getCompiler().getVS();
		
		// Push to fpu GPR value.
		if (getLocation().isGPR()) {
			GPR reg = ((GPRLocation)getLocation()).getGPR();
			
			asm.push(reg);
			pushFPU(X86Register.ESP, 0);
			asm.lea(X86Register.ESP, X86Register.ESP, 4);
		}
		
		// Push to fpu from local var.
		if (getLocation().isLocal()) {
			pushFPU(X86Register.ESP, ((LocalLocation)getLocation()).getOffset());
		}
		
		if (getLocation().isConstant()) {
			pushConstant();
			pushFPU(X86Register.ESP, 0);
			asm.lea(X86Register.ESP, X86Register.ESP, 4);
		}
		
		if (getLocation().isStack()) {
			pushFPU(X86Register.ESP, 0);
			asm.lea(X86Register.ESP, X86Register.ESP, 4);
		}
		
		cleanup();
		setLocation(new FPULocation());
		
		stack.getFPUStack().push(this);
	}
	
	/* (non-Javadoc)
	 * @see icebird.compiler.ncomp.vs.Item#uses(icebird.compiler.ncomp.X86Register)
	 */
	public boolean uses(X86Register r) {
		if (getLocation().isGPR()) {
			if (((GPRLocation)getLocation()).getGPR() == r) 
				return true;
		}
		
		return false;
	}
	
	/* (non-Javadoc)
	 * @see icebird.compiler.ncomp.vs.Item#release()
	 */
	public void release() {
		cleanup();
	}

	protected abstract WordItem cloneConstant();
	
	protected abstract void loadConstant(X86Register r);
	protected abstract void pushConstant();
	
	protected abstract void popFPU(GPR reg, int disp);
	protected abstract void pushFPU(GPR reg, int disp);
	
	/**
	 * Get word item located on GPR.
	 * @return WordItem
	 */
	public static WordItem getGPRWord(GPR reg, BasicType type) {
		if (type.isWide()) {
			throw new IllegalArgumentException("Cannot allocate word, for wide");
		}
		
		GPRLocation loc = new GPRLocation(reg);
		if (type == BasicType.Float) {
			return new FloatItem(loc);
		} else {
			return new IntegerItem(loc);
		}
	}
	
	/**
	 * Get word item located on stack(as local)
	 * @return WordItem
	 */
	public static WordItem getLocal(int offset, BasicType type) {
		if (type.isWide()) {
			throw new IllegalArgumentException("Cannot allocate word, for wide");
		}
		
		LocalLocation loc = new LocalLocation(offset);
		if (type == BasicType.Float) {
			return new FloatItem(loc);
		} else {
			return new IntegerItem(loc);
		}
	}
	
	/**
	 * Gets word located on operand stack.
	 * @return WordItem
	 */
	public static WordItem getStack(BasicType type) {
		if (type.isWide()) {
			throw new IllegalArgumentException("Cannot allocate word, for wide");
		}
		
		StackLocation loc = new StackLocation();
		if (type == BasicType.Float) {
			return new FloatItem(loc);
		} else {
			return new IntegerItem(loc);
		}
	}
}
